package emu.decvices.cartridge.mappers;

import emu.decvices.bus.BusDevice;
import emu.decvices.cartridge.Cartridge;

public class Mapper001 extends Mapper{
    /**
     * Banks
     * CPU $6000-$7FFF：8 KB PRG RAM Bank（可选）
     * CPU $8000-$BFFF：16 KB PRG ROM Bank，可切换或固定在第一个Bank中
     * CPU $C000-$FFFF：16 KB PRG ROM Bank，可固定在最后一个Bank或可切换
     * PPU $0000-$0FFF：4 KB 可切换 CHR Bank
     * PPU $1000-$1FFF：4 KB 可切换 CHR Bank
     * 通过写入 MMC1 控制寄存器，程序可以交换固定和可切换的 PRG ROM 库，或设置 32 KB PRG 库切换（如 BNROM），但大多数游戏使用默认设置，与 UxROM 类似。
     *
     * 与几乎所有其他映射器不同，MMC1 通过串行端口配置，以减少引脚数。CPU $8000-$FFFF 连接到一个通用移位寄存器。
     * 向$8000-$FFFF 中的任何地址写入设置了第 7 位（$80 至 $FF）的值，都会将移位寄存器清零，恢复到初始状态。
     * 要更改寄存器的值，CPU 需要写五次，其中第 7 位清零，所需数值的一位写入第 0 位（从数值的低位开始）。
     * 前四次写入时，MMC1 会将第 0 位移入移位寄存器。
     * 第五次写入时，MMC1 会将第 0 位和移位寄存器内容复制到由地址第 14 位和第 13 位选择的内部寄存器中，然后清空移位寄存器。
     * 只有在第五次写入时，地址才有意义，即使如此，也只有地址的第 14 位和第 13 位有意义，因为映射器看不到较低的地址位（与 PPU 寄存器的镜像类似）。
     * 第五次写入后，移位寄存器会自动清零，因此再次写入时不需要设置第 7 位来清零移位寄存器。
     *
     *
     *
     *  只用到5bit，控制寄存器
     *  如果最后一次写入的地址在$8000-$9FFF范围内，那么这5位数据会被用来设置控制寄存器，其含义如下：
     *
     * bit 0和bit 1：设置镜像模式。00 = 一屏镜像（低），01 = 一屏镜像（高），10 = 垂直镜像，11 = 水平镜像。
     * bit 2：设置PRG ROM bank的大小。0 = 32KB，1 = 16KB。
     * bit 3：设置PRG ROM bank的切换模式。
     *        0 = 固定在C000−FFFF，可切换8000−BFFF，
     *        1 = 固定在8000−BFFF，可切换C000−FFFF。
     * bit 4：设置CHR ROM bank的大小。0 = 8KB，1 = 4KB。
     * 如果最后一次写入的地址在A000−BFFF范围内，那么这5位数据会被用来切换8000−BFFF区域的PRG ROM bank。具体的切换方式取决于控制寄存器的设置。
     *
     * 如果最后一次写入的地址在C000−FFFF范围内，那么这5位数据会被用来切换C000−FFFF区域的PRG ROM bank。具体的切换方式取决于控制寄存器的设置。
     *
     * 如果最后一次写入的地址在$0000-$1FFF范围内，那么这5位数据会被用来切换CHR ROM bank。具体的切换方式取决于控制寄存器的设置。
     */

    /*
        4bit0
        -----
        CPPMM
        |||||
        |||++- Mirroring (0: one-screen, lower bank; 1: one-screen, upper bank;
        |||               2: vertical; 3: horizontal)
        |++--- PRG ROM bank mode (0, 1: switch 32 KB at $8000, ignoring low bit of bank number;
        |                         2: fix first bank at $8000 and switch 16 KB bank at $C000;
        |                         3: fix last bank at $C000 and switch 16 KB bank at $8000)
        +----- CHR ROM bank mode (0: switch 8 KB at a time; 1: switch two separate 4 KB banks)
     */
    private int controlRegister = 0;
    /*

        Sets the switchable PRG ROM bank to the value of A.

                      ;  A          MMC1_SR  MMC1_PB
        setPRGBank:   ;  000edcba    10000             Start with an empty shift register (SR).  The 1 is used
          sta $E000   ;  000edcba -> a1000             to detect when the SR has become full.
          lsr a       ; >0000edcb    a1000
          sta $E000   ;  0000edcb -> ba100
          lsr a       ; >00000edc    ba100
          sta $E000   ;  00000edc -> cba10
          lsr a       ; >000000ed    cba10
          sta $E000   ;  000000ed -> dcba1             Once a 1 is shifted into the last position, the SR is full.
          lsr a       ; >0000000e    dcba1
          sta $E000   ;  0000000e    dcba1 -> edcba    A write with the SR full copies D0 and the SR to a bank register
                      ;              10000             ($E000-$FFFF means PRG bank number) and then clears the SR.
          rts
     */
    private int shiftRegister = 0x10;//移位寄存器 00010000
    /*
        (0, 1: switch 32 KB at $8000, ignoring low bit of bank number;
|           2: fix first bank at $8000 and switch 16 KB bank at $C000;
|           3: fix last bank at $C000 and switch 16 KB bank at $8000)
     */
    private int prgBankMode = 3;
    private int prgBank;
    private boolean prgRamEnable;//是否启动prgRAM
    private byte[] prgRam = new byte[1024*32];//32kb prgRam
    private int chrBankMode;//(0: switch 8 KB at a time; 1: switch two separate 4 KB banks)
    private int chrBank0;
    private int chrBank1;

    private int[] ram = new int[1024*8];
    private CpuDevice cpuDevice = new CpuDevice();
    private PpuDevice ppuDevice = new PpuDevice();

    public Mapper001(Cartridge cartridge) {
        super(cartridge);
    }

    @Override
    public BusDevice getCpuDevice() {
        return cpuDevice;
    }

    @Override
    public BusDevice getPpuDevice() {
        return ppuDevice;
    }

    private class PpuDevice extends BusDevice{
        private int[] vram = new int[0x2000];
        @Override
        public int fromDeviceRead(int address) {
            if(address<=0x1FFF){
                if(romHeader.getChrRomChunks()==0){
                    return vram[address]&0xFF;
                }else {
                    if (chrBankMode == 0) {//switch 8 KB at a time
                        return chrData[chrBank0 * 0x2000 + address] & 0xFF;
                    } else {
                        return address < 0x1000 ? chrData[chrBank0 * 0x1000 + address] & 0xFF : chrData[chrBank1 * 0x1000 + (address & 0x0FFF)] & 0xFF;
                    }
                }
            }
            return -1;
        }

        @Override
        public boolean toDeviceWrite(int address, int data) {
            if(address<=0x1FFF) {
                if (romHeader.getChrRomChunks() == 0) {
                    vram[address] = data;
                    return true;
                }
            }
            return false;
        }
    }

    private class CpuDevice extends BusDevice{

        @Override
        public int fromDeviceRead(int address) {
            if(address>=0x6000&&address<=0x7FFF){
               return ram[address&0x1FFF]&0xFF;
            }
            //CPU可寻址prgRom的总线地址范围
            if(address>=0x8000 && address<=0xFFFF) {
                switch (prgBankMode){
                    case 0:
                    case 1:
                        return prgData[prgBank*0x8000+(address&0x7FFF)]&0xFF;
                    case 2:
                        if(address<0xC000){
                            return prgData[address&0x3FFF]&0xFF;//first bank
                        }else{
                            return prgData[prgBank*0x4000+(address&0x3FFF)]&0xFF;
                        }
                    case 3:
                        if(address<0xC000){
                            return prgData[prgBank*0x4000+(address&0x3FFF)]&0xFF;
                        }else{
                            return prgData[prgData.length-0x4000+(address&0x3FFF)]&0xFF;//last bank
                        }
                }
            }
            return -1;
        }

        @Override
        public boolean toDeviceWrite(int address, int data) {
            if(address>=0x6000&&address<=0x7FFF){
                ram[address&0x1FFF] = data;
                return true;
            }
            if(address>=0x8000) {
                if ((data & 0x80) > 0) {
                    reset();//重置
                } else {
                    if ((shiftRegister & 0x01) == 1) {
                        shift(data);
                        process(address);
                        shiftRegister = 0x10;
                    } else {
                        shift(data);
                    }
                }
                return true;
            }
            return false;
        }
        private void shift(int data){
            shiftRegister >>= 1;
            shiftRegister |= (data & 0x01) << 4;
        }
        /*
            要重置映射器（清零移位寄存器并将 PRG ROM 存储模块模式设置为 3（将最后一个存储模块固定在 $C000，并允许切换位于 $8000 的 16 KB 存储模块），
            只需向第 7 位为 1 的任何 ROM 地址写入一次即可
         */
        public void reset(){
            prgBankMode = 3;
            chrBankMode = 0;
            chrBank0=0;
            chrBank1=0;
            prgBank=0;
            controlRegister = 0;
            shiftRegister = 0x10;
        }

        public void process(int address){
            if(address<=0x9FFF) {
            /*
                4bit0
                -----
                CPPMM
                |||||
                |||++- Mirroring (0: one-screen, lower bank; 1: one-screen, upper bank;
                |||               2: vertical; 3: horizontal)
                |++--- PRG ROM bank mode (0, 1: switch 32 KB at $8000, ignoring low bit of bank number;
                |                         2: fix first bank at $8000 and switch 16 KB bank at $C000;
                |                         3: fix last bank at $C000 and switch 16 KB bank at $8000)
                +----- CHR ROM bank mode (0: switch 8 KB at a time; 1: switch two separate 4 KB banks)
             */
                switch (shiftRegister & 0x03) {
                    case 0:
                        setMirror(Cartridge.Mirror.ONESCREEN_LO);
                        break;
                    case 1:
                        setMirror(Cartridge.Mirror.ONESCREEN_HI);
                        break;
                    case 2:
                        setMirror(Cartridge.Mirror.VERTICAL);
                        break;
                    case 3:
                        setMirror(Cartridge.Mirror.HORIZONTAL);
                        break;
                }
                prgBankMode = (shiftRegister&0x0C)>>2;
                chrBankMode = (shiftRegister&0x10)>>4;
            }else if (address >= 0xA000 && address <= 0xBFFF) {
                    /*
                        CHR bank 0 (internal, $A000-$BFFF)
                        4bit0
                        -----
                        CCCCC
                        |||||
                        +++++- Select 4 KB or 8 KB CHR bank at PPU $0000 (low bit ignored in 8 KB mode)
                     */
                chrBank0 = shiftRegister & (chrBankMode == 0 ? 0x1E : 0x1F);
            } else if (address >= 0xC000 && address <= 0xDFFF) {
                    /*
                        4bit0
                        -----
                        CCCCC
                        |||||
                        +++++- Select 4 KB CHR bank at PPU $1000 (ignored in 8 KB mode)
                     */
                chrBank1 = shiftRegister & 0x1F;
            } else if (address >= 0xE000 && address <= 0xFFFF) {
                    /*
                        4bit0
                        -----
                        RPPPP
                        |||||
                        |++++- Select 16 KB PRG ROM bank (low bit ignored in 32 KB mode)
                        +----- MMC1B and later: PRG RAM chip enable (0: enabled; 1: disabled; ignored on MMC1A)
                               MMC1A: Bit 3 bypasses fixed bank logic in 16K mode (0: fixed bank affects A17-A14;
                               1: fixed bank affects A16-A14 and bit 3 directly controls A17)
                     */
                if(prgBankMode < 2){
                    prgBank = (shiftRegister & 0x0E);
                }else{
                    prgBank = shiftRegister & 0x0F;
                }
                prgRamEnable = (shiftRegister & 0x10) == 0;//MMC1B
            }
        }
    }
}
